package com.sec.android.app.myfiles.navigation;

import android.content.Context;
import android.util.SparseArray;

import com.sec.android.app.myfiles.facade.MyFilesFacade;
import com.sec.android.app.myfiles.fragment.AbsMyFilesFragment;
import com.sec.android.app.myfiles.log.Log;
import com.sec.android.app.myfiles.module.abstraction.FileRecord;
import com.sec.android.app.myfiles.module.abstraction.FileRecord.StorageType;
import com.sec.android.app.myfiles.module.cloud.CloudFileRecord;
import com.sec.android.app.myfiles.navigation.NavigationChangedObserver.NavigationChangedListener;
import com.sec.android.app.myfiles.operation.FileOperator;
import com.sec.android.app.myfiles.util.PreferenceUtils;
import com.sec.android.app.myfiles.util.UiUtils;

import java.util.ArrayList;
import java.util.Stack;

public class NavigationManager {
    private static SparseArray<NavigationManager> mNavigationMap = new SparseArray<>();

    private Stack<NavigationInfo> mNavigationStack;
    private NavigationChangedObserver mNavigationChangedObserver = new NavigationChangedObserver();
    private int mProcessId;
    private Stack<NavigationInfo> mSaveNavigationStack;
    private boolean mFromPrivateBox = false;
    private boolean mFromPrivateStorage = false;

    private NavigationManager(int processId) {
        mProcessId = processId;
        mNavigationStack = new Stack<>();
    }

    /**
     * Create NavigationManger Instance and return It works based on Singleton
     *
     * @return NavigationManager getInstance
     * @author jae.bae
     */
    public static NavigationManager getInstance(int processId) {
        NavigationManager ret = mNavigationMap.get(processId);
        if (ret == null) {
            ret = new NavigationManager(processId);
            mNavigationMap.put(processId, ret);
        }

        return ret;
    }

    public int getProcessId() {
        return mProcessId;
    }

    /**
     * Only 1 activity permitted to ActionMode in multiwindow
     *
     * @return
     */
    public static boolean isPossibleToActionMode() {
        return isPossibleToActionMode(-1);
    }

    public static ArrayList<Integer> getProcessIdList() {
        ArrayList<Integer> ret = new ArrayList<>();
        for (int i = 0; i < mNavigationMap.size(); i++) {
            ret.add(mNavigationMap.keyAt(i));
        }
        return ret;
    }

    public static boolean isPossibleToActionMode(int exceptProcessId) {
        boolean bRet = true;

        for (int i = 0; i < mNavigationMap.size(); i++) {
            int key = mNavigationMap.keyAt(i);
            if (key != exceptProcessId) {
                NavigationManager naviMgr = mNavigationMap.get(key);
                try {
                    NavigationInfo curInfo = naviMgr.getCurInfo();
                    if (curInfo != null) {
                        AbsMyFilesFragment fragment = curInfo.getCurFragment();
                        if (fragment != null) {
                            if (fragment.isSelectActionMode() ||
                                    FileOperator.isWorking() ||
                                    curInfo.isCompressPreview() ||
                                    curInfo.isSelectDestinationPath() ||
                                    curInfo.isSelectOpDestination()) {
                                bRet = false;
                                break;
                            }
                        }
                    }
                } catch (NullPointerException e) {
                    Log.e(NavigationManager.class, e.getMessage());
                }
            }
        }

        return bRet;
    }

    public static void clearInstance(int processId) {
        NavigationManager navigationMgr = mNavigationMap.get(processId);
        if (navigationMgr != null) {
            navigationMgr.clear();
            navigationMgr.mNavigationStack = null;
            navigationMgr.mSaveNavigationStack = null;

            mNavigationMap.remove(processId);
        }
    }

    /**
     * <pre>
     * register listener for enter(), leave() method.
     * registered listener is executed when enter(), leave() is called .
     * </pre>
     *
     * @return void
     * @see #enter enter
     * @see #leave leave
     */
    public void registerNavigationChangedListener(NavigationChangedListener listener) {
        mNavigationChangedObserver.add(listener);
    }

    public void unregisterNavigationChangedListener(NavigationChangedListener listener) {
        mNavigationChangedObserver.remove(listener);
    }

    public NavigationInfo enter(NavigationInfo info) {
        if (info != null) {
            NavigationInfo preInfo = getCurInfo();

            if (!info.equals(preInfo)) {
                mNavigationStack.push(info);
                mNavigationChangedObserver.notifyPathChanged(preInfo, info);
            } else {
                Log.d(this, "enter : ignore same record");
            }

            Log.d(this, "enter() size=" + mNavigationStack.size());
        }

        return info;
    }

    public NavigationInfo enterWithClean(NavigationInfo info) {
        if (info != null) {
            NavigationInfo preInfo = getCurInfo();
            clear();

            mNavigationStack.push(info);
            mNavigationChangedObserver.notifyPathChanged(preInfo, info);
            Log.d(this, "enterWithClean() size=" + mNavigationStack.size());
        }

        return info;
    }

    public void addNavigationInfo(int index, NavigationInfo info) {
        int size = mNavigationStack.size();
        if (index < 0) {
            index = 0;
        } else if (index > size) {
            index = size;
        }
        if (!mNavigationStack.get(index).equals(info)) {
            mNavigationStack.add(index, info);
        }
    }

    public void removeNavigationInfo(int index) {
        int size = mNavigationStack.size();
        if (size > 0 && index >= 0 && index < size) {
            mNavigationStack.remove(index);
        }
    }

    public NavigationInfo backCurNavigationMode() {
        NavigationInfo preInfo = getCurInfo();
        NavigationInfo ret = clearCurNavigationStack();

        if (!isEmpty() && preInfo != null) {
            AbsMyFilesFragment fragment = preInfo.getCurFragment();
            if (fragment != null) {
                Context context = fragment.context();
                if (context != null) {
                    clearNotExistTopInfo(context, true);
                    if (!isEmpty()) {
                        ret = mNavigationStack.peek();
                    } else {
                        FileRecord record = UiUtils.getHomeRecord(context, preInfo);
                        ret = NavigationInfo.getInstance(NavigationInfo.NavigationMode.Normal, record);
                    }
                }
            }
        }

        mNavigationChangedObserver.notifyPathChanged(preInfo, ret);
        return ret;
    }

    public NavigationInfo clearCurNavigationStack() {
        NavigationInfo ret = null;

        if (!isEmpty()) {
            NavigationInfo cur = mNavigationStack.pop();
            while (!isEmpty() && cur != null) {
                ret = mNavigationStack.peek();

                if (cur.getNavigationMode() != ret.getNavigationMode()) {
                    break;
                } else {
                    cur = mNavigationStack.pop();
                }
            }
        }

        Log.d(this, "clearCurNavigationStack() size=" + mNavigationStack.size());
        return ret;
    }

    public NavigationInfo leave() {
        NavigationInfo ret = null;

        Log.d(this, "leave()");
        NavigationInfo preInfo = getCurInfo();

        if (!isEmpty() && mNavigationStack.pop() != null) {
            ret = getCurInfo();
            if (ret != null) {
                if (mFromPrivateStorage && ret.getNavigationMode() == NavigationInfo.NavigationMode.Normal) {
                    mFromPrivateStorage = false;
                }
            }
            mNavigationChangedObserver.notifyPathChanged(preInfo, ret);
        }

        Log.d(this, "leave() size=" + mNavigationStack.size());

        return ret;
    }

    public NavigationInfo leave(Context context) {
        Log.d(this, "leave()");
        NavigationInfo preInfo = getCurInfo();

        if (!isEmpty()) {
            if (mNavigationStack.peek() != null) {
                if (clearNotExistTopInfo(context)) {
                    if (preInfo != null && preInfo.equals(getCurInfo()) && !mNavigationStack.isEmpty()) {
                        mNavigationStack.pop();
                    }
                }
                NavigationInfo curInfo = getCurInfo();
                if (curInfo != null) {
                    if (mFromPrivateStorage && curInfo.getNavigationMode() == NavigationInfo.NavigationMode.Normal) {
                        mFromPrivateStorage = false;
                    }
                }

                mNavigationChangedObserver.notifyPathChanged(preInfo, curInfo);
            }
        }

        Log.d(this, "leave() size=" + mNavigationStack.size());

        return getCurInfo();
    }

    public NavigationInfo leaveWithEnter(NavigationInfo info) {
        NavigationInfo ret = null;

        Log.d(this, "leaveWithEnter()");
        NavigationInfo oldPrevInfo = getCurInfo();
        if (info != null) {
            if (!isEmpty() && mNavigationStack.pop() != null) {
                Log.d(this, "after leave() size=" + mNavigationStack.size());
                NavigationInfo newPrevInfo = getCurInfo();
                if (!info.equals(newPrevInfo)) {
                    mNavigationStack.push(info);
                }
                ret = info;
                mNavigationChangedObserver.notifyPathChanged(oldPrevInfo, info);
                Log.d(this, "after enter() size=" + mNavigationStack.size());
            }
        }
        return ret;
    }

    private boolean clearNotExistTopInfo(Context context) {
        return clearNotExistTopInfo(context, false);
    }

    private boolean clearNotExistTopInfo(Context context, boolean keepTop) {
        boolean bExist = false;

        if (mNavigationStack != null) {
            FileRecord currentRecord = null;
            NavigationInfo currentInfo = getCurInfo();
            if (currentInfo != null) {
                currentRecord = currentInfo.getCurRecord();
            }
            if (currentRecord != null && !mNavigationStack.isEmpty()) {
                while (true) {
                    NavigationInfo topInfo = getCurInfo();
                    if (topInfo == null) {
                        break;
                    }
                    FileRecord topRecord = topInfo.getCurRecord();
                    if (topRecord != null) {
                        boolean isExists = topRecord.exists(context);
                        if (topRecord.getStorageType() == StorageType.Cloud) {
                            isExists = (((CloudFileRecord) topRecord).existsCheckWhenBack(context) || topRecord.isRoot());
                        } else if (topRecord.getStorageType() == StorageType.Recent) {
                            isExists = PreferenceUtils.getShowRecentFiles(context);
                        } else if (topRecord.getStorageType() == StorageType.Blank) {
                            isExists = false;
                        }
                        boolean dontRemoveTop = keepTop || (!currentRecord.equals(topRecord) || (currentInfo.getNavigationMode() != topInfo.getNavigationMode()));
                        if (isExists && dontRemoveTop) {
                            bExist = true;
                            break;
                        }
                    }
                    if (!mNavigationStack.isEmpty()) {
                        mNavigationStack.pop();
                    } else {
                        break;
                    }
                }
            }
        }
        return bExist;
    }

    public void refreshNotify() {
        if (!isEmpty()) {
            NavigationInfo info = getCurInfo();
            mNavigationChangedObserver.notifyPathChanged(info, info);
        }
    }

    public NavigationInfo getCurInfo() {
        NavigationInfo ret = null;

        if (!isEmpty()) {
            ret = mNavigationStack.peek();
        }

        return ret;
    }

    public NavigationInfo getLastInfo() {
        NavigationInfo ret = null;

        if (!isEmpty()) {
            ret = mNavigationStack.get(0);
        }

        return ret;
    }

    public boolean goUp(Context context) {
        boolean bRet = true;

        if (!isEmpty()) {
            NavigationInfo currentInfo = mNavigationStack.pop();

            NavigationInfo preInfo = null;
            FileRecord preRecord = null;

            FileRecord currentRecord;
            FileRecord parentRecord = null;
            if (!isEmpty()) {
                preInfo = mNavigationStack.peek();
                preRecord = preInfo.getCurRecord();

                currentRecord = currentInfo.getCurRecord();
                parentRecord = currentRecord.getParent();
            }

            if (parentRecord != null && preRecord != null) {
                if (parentRecord.equals(preRecord)) {
                    mNavigationChangedObserver.notifyPathChanged(currentInfo, preInfo);
                } else {
                    NavigationInfo info = NavigationInfo.getInstance(currentInfo.getNavigationMode(), parentRecord);
                    mNavigationStack.push(currentInfo);
                    mNavigationStack.push(info);
                    mNavigationChangedObserver.notifyPathChanged(currentInfo, info);
                }
            } else {
                mNavigationStack.push(currentInfo);
                MyFilesFacade.goHome(getProcessId(), currentInfo, context);
            }
        }

        Log.d(this, "goUp() ret = " + bRet);
        return bRet;
    }

    public boolean isEmpty() {
        if (mNavigationStack != null) {
            return mNavigationStack.isEmpty();
        }
        return true;
    }

    public void clear() {
        mNavigationStack.clear();
    }

    public int getDepth() {
        return mNavigationStack.size();
    }

    public boolean isFromPrivateBox() {
        return mFromPrivateBox;
    }

    public void setFromPrivateBox(boolean fromPrivateBox) {
        mFromPrivateBox = fromPrivateBox;
    }

    public boolean isFromPrivateStorage() {
        return mFromPrivateStorage;
    }

    public void setFromPrivateStorage(boolean fromPrivateStorage) {
        mFromPrivateStorage = fromPrivateStorage;
    }

    public void saveNavigationStack() {
        if (mSaveNavigationStack == null) {
            mSaveNavigationStack = new Stack<>();
        } else {
            mSaveNavigationStack.clear();
        }
        mSaveNavigationStack.addAll(mNavigationStack);
    }

    private boolean restoreNavigationStack() {
        if (!mSaveNavigationStack.isEmpty()) {
            mNavigationStack.clear();
            return mNavigationStack.addAll(mSaveNavigationStack);
        } else {
            return false;
        }
    }

    public void goToRestoreNavigationStack(NavigationInfo mCurInfo) {
        restoreNavigationStack();
        mNavigationStack.push(mCurInfo);
        leave();
    }
}
